<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>毽子舞Part2</title>
    <script src="http://192.168.1.168:8080/@mediapipe/pose/pose.js" crossorigin="anonymous"></script>
</head>

<body>
    <p>
        <button onclick="openMedia()">打开</button>
        <button onclick="closeMedia()">关闭</button>
        <button onclick="startDetect()">开始检测</button>
        <button onclick="stopDetect()">停止检测</button>
        
        <button onclick="changeCamera()">切换摄像头</button>
    </p>
    <div class="video-canvas" style="position: relative;">
        <canvas id="drawCanvas" width="720" height="1280" style="position: absolute; z-index: 1;"></canvas>
        <video id="video" class="bg" width="720" height="1280"
            style="position: absolute; z-index: -1; transform: scale(0.3,0.3);transform-origin: .125rem .125rem;"></video>
        <video id="video_simple" style="position: absolute; z-index: -2;" src="http://192.168.1.168:8080/part2.mp4"
            controls="true"></video>
    </div>

    <script>
        var video1 = document.querySelector('video');
        var canvas = document.getElementById('drawCanvas');
        //获得 2d 上下文对象，这个对象就是用于描绘和写字的对象
        var ctx = canvas.getContext('2d');
        var mediaStreamTrack;
        // flag_videoisOpen用于记录摄像头是否开启的状态
        var flag_videoisOpen = false;
        // flag_detectisStart用于记录姿态检测是否开始的状态
        var flag_detectisStart = false;
        // flag_count用于记录当前状态下能否进行俯卧撑的计数
        var flag_count = false;
        // score 用于记录得分
        var score_l = 0;
        var score_r = 0;
        // 一堆兼容代码

        window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
        if (navigator.mediaDevices === undefined) {
            navigator.mediaDevices = {};
        }
        if (navigator.mediaDevices.getUserMedia === undefined) {
            navigator.mediaDevices.getUserMedia = function (constraints) {
                var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
                if (!getUserMedia) {
                    return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
                }
                return new Promise(function (resolve, reject) {
                    getUserMedia.call(navigator, constraints, resolve, reject);
                })
            }
        }

        //摄像头调用配置
        var mediaOpts = {
            audio: false,
            video: true,
            video: { facingMode: 'environment', width: 720, height: 1280 }, // 或者 "user"
        }

        // 回调
        function successFunc(stream) {
            mediaStreamTrack = stream;
            // video1 = document.querySelector('video');
            if ("srcObject" in video1) {
                video1.srcObject = stream;
            } else {
                video1.src = window.URL && window.URL.createObjectURL(stream) || stream;
            }
            video1.play();
            console.log("摄像头已开启");
            flag_videoisOpen = true;
        }
        function errorFunc(err) {
            alert(err.name);
        }

        // 正式启动摄像头
        function openMedia() {
            if (!flag_videoisOpen)
                navigator.mediaDevices.getUserMedia(mediaOpts).then(successFunc).catch(errorFunc);
        }

        //关闭摄像头，同时停止检测关键点
        function closeMedia() {
            // 重置宽或高可以清空画布
            canvas.width = canvas.width;
            if (flag_videoisOpen) {
                mediaStreamTrack.getVideoTracks().forEach(function (track) {
                    track.stop();
                })
                console.log("摄像头已关闭");
                flag_videoisOpen = false;
            } else {
                alert("摄像头没有开启");
            }
        }

        // 这个函数用于切换前后摄像头，
        function changeCamera() {
            closeMedia();
            stopDetect();
            switch (mediaOpts.video.facingMode) {
                default:
                    mediaOpts.video = { facingMode: 'environment', width: 720, height: 1280 };
                    // pose.setOptions.selfieMode = false;
                    break;
                case 'user':
                    mediaOpts.video = { facingMode: 'environment', width: 720, height: 1280 };
                    video1.setAttribute("style", "z-index: -1;")
                    setting.selfieMode = false;
                    break;
                case 'environment':
                    mediaOpts.video = { facingMode: 'user', width: 720, height: 1280 };
                    video1.setAttribute("style", "transform: rotateY(180deg); z-index: -1;")
                    setting.selfieMode = true;
                    break;
            }
            console.log("当前摄像头已被切换为" + mediaOpts.video.facingMode);
            console.log("自拍模式" + pose.setOptions.selfieMode);
            openMedia();
        }

        const pose = new Pose({
            locateFile: (file) => {
                return `http://192.168.1.168:8080/@mediapipe/pose/${file}`;
            }
        });

        // MediaPipe Pose的相关配置 
        var setting = {
            selfieMode: false,
            // modelComplexity: 0,
            // upperBodyOnly: true,
            // smoothLandmarks: true,
            // enableSegmentation: false,
            // smoothSegmentation: true,
            // minDetectionConfidence: 0.8,
            // minTrackingConfidence: 0.5
            modelComplexity: 2,
            smoothLandmarks: true,
            enableSegmentation: true,
            smoothSegmentation: true,
            minDetectionConfidence: 0.8,
            minTrackingConfidence: 0.8
        }

        // results就是检测出的人体关键点
        function printResults(results) {
            writeText("左计数：" + score_l, [60, 60]);
            writeText("右计数：" + score_r, [60, 80]);
            // 判断人体是否在画面中
            switch (manInFrame(results)) {
                default:
                    writeText("画面中没有人");
                    break;
                case -1:
                    writeText("画面中没有人");
                    break;
                case 0:
                    writeText("人没有完整出现在画面中");
                    break;
                case 1:
                    detectAction2(results);
                    break;
            }
        }
        pose.onResults(printResults);
        // flag_judge1用于记录是否检测到第一次不限定方向的动作，检测到则置为false
        var flag_judge1 = true;
        // flag_direction用于判断方向，1表示左，-1表示右
        var flag_direction = 1;
        // 当人体全部处于画面中时，调用这个函数开始判断动作
        function detectAction2(results) {
            // 手部用手腕代替
            Point_right_hand = {
                x: results.poseLandmarks[POSE_LANDMARKS.RIGHT_WRIST].x * video1.width,
                y: results.poseLandmarks[POSE_LANDMARKS.RIGHT_WRIST].y * video1.height
            };
            // 腰部
            Point_right_hip = {
                x: results.poseLandmarks[POSE_LANDMARKS.RIGHT_HIP].x * video1.width,
                y: results.poseLandmarks[POSE_LANDMARKS.RIGHT_HIP].y * video1.height
            };
            // 膝盖
            Point_right_knee = {
                x: results.poseLandmarks[POSE_LANDMARKS.RIGHT_KNEE].x * video1.width,
                y: results.poseLandmarks[POSE_LANDMARKS.RIGHT_KNEE].y * video1.height
            };
            // 脚踝
            Point_right_ankle = {
                x: results.poseLandmarks[POSE_LANDMARKS.RIGHT_ANKLE].x * video1.width,
                y: results.poseLandmarks[POSE_LANDMARKS.RIGHT_ANKLE].y * video1.height
            };
            Point_left_hand = {
                x: results.poseLandmarks[POSE_LANDMARKS.LEFT_WRIST].x * video1.width,
                y: results.poseLandmarks[POSE_LANDMARKS.LEFT_WRIST].y * video1.height
            };
            Point_left_hip = {
                x: results.poseLandmarks[POSE_LANDMARKS.LEFT_HIP].x * video1.width,
                y: results.poseLandmarks[POSE_LANDMARKS.LEFT_HIP].y * video1.height
            };
            Point_left_knee = {
                x: results.poseLandmarks[POSE_LANDMARKS.LEFT_KNEE].x * video1.width,
                y: results.poseLandmarks[POSE_LANDMARKS.LEFT_KNEE].y * video1.height
            };
            Point_left_ankle = {
                x: results.poseLandmarks[POSE_LANDMARKS.LEFT_ANKLE].x * video1.width,
                y: results.poseLandmarks[POSE_LANDMARKS.LEFT_ANKLE].y * video1.height
            };
            PointArray1 = [Point_right_hand, Point_left_knee, Point_left_ankle, Point_right_hip, Point_right_knee, Point_right_ankle];
            PointArray2 = [Point_left_hand, Point_right_knee, Point_right_ankle, Point_left_hip, Point_left_knee, Point_left_ankle];

            // 不限定第一次的方向，看哪边先完成
            if (flag_judge1) {
                if (judgePart2(PointArray1)) {
                    flag_direction = 1;
                    flag_judge1 = false;
                    score_r += 1;
                }
                if (judgePart2(PointArray2)) {
                    flag_direction = -1;
                    flag_judge1 = false;
                    score_l += 1;
                }
            } else {
                switch (flag_direction) {
                    case 1:
                        if (judgePart2(PointArray2)) {
                            flag_direction *= -1;
                            score_l += 1;
                        }

                        break;
                    case -1:
                        if (judgePart2(PointArray1)) {
                            flag_direction *= -1;
                            score_r += 1;
                        }
                        break;
                }
            }
        }

        /**
         * 这个函数判断数组中的点是否满足毽子舞Part2的要求
         * 要求
         * 1、手与膝盖相碰
         * 2、脚踝高于另一边的膝盖
         * 3、另一边的腰、膝盖、脚踝近似直线
         */
        var judgevalue = 120;
        function judgePart2(PointArray) {
            if (getDistance(PointArray[0], PointArray[1]) < judgevalue && getDistance(PointArray[2], PointArray[4]) < judgevalue && getAngle(PointArray[3], PointArray[4], PointArray[5]) > 150)
                return true;
            return false;
        }

        // 这个函数用于描绘全部关键点，验证关键点检测是否正确
        function drawKeypoints(results) {
            for (let index = 0; index < results.poseLandmarks.length; index++) {
                drawCircle({ x: results.poseLandmarks[index].x * video1.width, y: results.poseLandmarks[index].y * video1.height }, 5);
            }
        }
        // 这个函数用于求两点之间的距离
        function getDistance(point1, point2) {
            return (Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)))

        }

        // 获取三点所成直线角度
        function getAngle(B, A, C) {
            var AB = Math.sqrt(Math.pow(A.x - B.x, 2) + Math.pow(A.y - B.y, 2));
            var AC = Math.sqrt(Math.pow(A.x - C.x, 2) + Math.pow(A.y - C.y, 2));
            var BC = Math.sqrt(Math.pow(B.x - C.x, 2) + Math.pow(B.y - C.y, 2));
            var cosA = (
                Math.pow(AB, 2) + Math.pow(AC, 2) - Math.pow(BC, 2)
            ) / (
                    2 * AB * AC
                );
            var angleA = Math.round(Math.acos(cosA) * 180 / Math.PI);
            return angleA;
            // 得到angleA角度：45°

            /*
                AB      = 开根( (A.X-B.X)² + (A.Y-B.Y)² ）
                AC      =       A.X-C.X      A.Y-C.Y
                BC      =       B.X-C.X      B.Y-C.Y
                cosA   = (AB²+AC²-BC²) / (2*AB*AC)
                angleA  = Math.acos(cosA)*180/Math.PI
            */
        }

        // 这个函数用于画线段
        function drawLine(start, end) {
            ctx.beginPath(); //新建一条path
            ctx.moveTo(start.x, start.y); //把画笔移动到指定的坐标
            ctx.lineTo(end.x, end.y);  //绘制一条从当前位置到指定坐标(200, 50)的直线.
            //闭合路径。会拉一条从当前点到path起始点的直线。如果当前点与起始点重合，则什么都不做
            ctx.closePath();
            ctx.stroke(); //绘制路径。
        }

        // 这个函数用于画点（圆）
        function drawCircle(center, r = 20) {
            ctx.beginPath();
            // 参数：圆心X，圆心Y，半径，开始弧度，结束弧度，false顺时针
            ctx.arc(center.x, center.y, r, 0, 2 * Math.PI, false);
            ctx.stroke();
        }

        // 这个函数用于检测画面中是否有完整的人体
        function manInFrame(results) {
            if (results.poseLandmarks == undefined) {
                return -1; //画面中不存在人
            }
            else {
                for (i = 0; i < 33; i++) {
                    if (results.poseLandmarks[i].x >= 1 || results.poseLandmarks[i].y >= 1) {
                        return 0;  //画面中的人没有完全出现在画面中
                    }
                }
                return 1; //画面有完整的人
            }
        }

        var fillStyles = ["#3f48cc", "#ff7f27", "#0ed145", "#4f61a0", "#fff200"];
        // 这个函数用于写字
        function writeText(text, position = [20, 20], font = '20px Arial', fillStyle = '#FF0000') {
            ctx.font = font;      	//设置字号和字体
            ctx.fillStyle = fillStyles[Math.floor(Math.random() * 5)];  	//设置字体颜色
            ctx.fillText(text, position[0], position[1]);
        }

        // 这个函数用于对当前画面进行人体关键点检测
        function detectKeypoints() {
            // 重置宽或高可以清空画布
            canvas.width = canvas.width;
            pose.send({ image: video1 })
        }

        // 这个函数用于启动关键点检测
        function startDetect() {
            pose.setOptions(setting);
            console.log(pose);
            if (flag_videoisOpen) {
                // 这个定时器需要在此函数外停止
                window.setInte = setInterval(detectKeypoints, 200);
                console.log("正在加载模型");
                flag_detectisStart = true;
            } else {
                alert("请先开启摄像头");
            }
        }

        // 这个函数用于停止关键点检测
        function stopDetect() {
            // 重置宽或高可以清空画布
            canvas.width = canvas.width;
            if (flag_detectisStart) {
                clearInterval(window.setInte);
                console.log("关键点检测已停止");
            } else {
                alert("关键点检测没有开始");
            }
        }
    </script>
</body>

</html>